<script type="text/javascript">
var scriptLibraries = MyOpenSpace.ClientLibraries.Scripts;
var includeLib = MyOpenSpace.ClientLibraries.includeScript;
includeLib(scriptLibraries.MYSPACE_WIDGETS);

</script>

<script type="text/javascript">


// for storage chapter - use cookies or app data store
var useCookieStorage = false;

//app data store code version - V1 (direct) or V2 (local store)
var getAppDataV1 = false;


/**
 * Tic-Tac-Toe static object controller namespace
 */
var TTT = {
   Keys: {
       NAME: "name",
       SCORE: "score"
   },
   RequestKeys: {
       VIEWERDATA: "viewer_data",
       VIEWER: "v",
       VIEWER_FRIENDS: "vf",
       VIEWER_PHOTOS: "vp"
   },
   Players: {
       Computer: "O",
       Human: "X"
   },
   /**
    * Actual Tic-Tac-Toe game engine.
    */
   Game: function(){

      var me = this;
      var cellbackEmpty = "#555";
      var cellbackWin = "#88E";
      var cellbackCustomBG = "";

      this.customBG = new TTT.CustomBackground();

      /**
       * Clear the game and reset
       */
      this.clear = function(){
         var i;
         for(i = 0; i < this.gameCells.length; i++){
            this.gameCells[i].innerHTML = "";

            if(this.customBG.usingCustomBG()){
               this.gameCells[i].style.backgroundColor = this.customBG.cellbackCustomBG;
            }
            else{
               this.gameCells[i].style.backgroundColor = cellbackEmpty;
            }
         }
         this.moves = new Array(9);
         this.hasWinner = false;
         this.winningCells = null;
         this.currentPlayer = TTT.Players.Human;
      }

      /**
       * Colors background on winning cells
       */
      this.markWinningCells = function(){
         if(!this.winningCells) return;
         for(var i = 0; i < this.winningCells.length; i++){
            this.gameCells[this.winningCells[i]].style.backgroundColor = cellbackWin;
         }
      }

      /**
       * Flag to indicate a winner was found
       */
      this.hasWinner = false;

      /**
       * Identify the cells in the winning line
       */
      this.winningCells = null;

      /**
       * Last person to win - player or computer
       */
      this.lastWinner = null;

      /**
        * The player who is currently allowed to make a move
        */
       this.currentPlayer = TTT.Players.Human;

      /**
       * The ID of your opponent
       */
      this.opponentId = null;

	  /**
       * Flag to set if this is a vs. computer
       * or vs. human game
       */
      this.isHumanOpponent = false;


      function init(){
         me.displayBoardElem = document.getElementById("gameboard");
         me.gameCells = me.displayBoardElem.getElementsByTagName("td");
         me.moves = new Array(9);
         me.messageBlock = document.getElementById("messages");
         me.clear();
      }

      init();
   }
};

/**
 * Game object method defintions
 */
TTT.Game.prototype = {

   writeMessage: function(message, color){
       if(color){
           this.messageBlock.innerHTML += "<span style='color:" + color + ";'>" + message + "</span>";
       }
       else{
           this.messageBlock.innerHTML += message;
       }
   },

   /**
    * Makes a move and records the result.
    * Draws the move on the game board
    * @param {Object} player
    * @param {Object} position
    */
   makeMove : function(player, position){
       if(player !== this.currentPlayer) return;

       this.lookForWin();
       if(!this.hasWinner){
           if(!this.moves[position]){
               this.moves[position] = player;
               this._drawMove(player, position);
               this.lookForWin();
           }
           else{
               this.writeMessage("Invalid Move for " + player, "red");
           }

           this.currentPlayer = (player === TTT.Players.Human) ? TTT.Players.Computer : TTT.Players.Human;
       }
       if(this.hasWinner){
           this.markWinningCells();
       }
   },
   /**
    * Draws the player's mark in the cell
    * @param {Object} player
    * @param {Object} position
    */
   _drawMove: function(player, position){
       this.gameCells[position].innerHTML = player;
   },

   /**
    * Intense AI to compete in most difficult tick-tack-toe tournaments
    */
   makeComputerMove : function(){
       var die = 50;
       var pos;
       var found = false;
       var i=0;
       while(!found && i++ < die){
           pos = new Date().getMilliseconds() % 10;
           if(pos == 9) pos = 8;
           found = (!this.moves[pos]);
       }
       if(!found){
           for(i=0; i < 9; i++){
               if(!this.moves[i]){
                   this.makeMove(TTT.Players.Computer, i);
                   return;
               }
           }
           alert('no move');
           return;
       }
       else{
           this.makeMove(TTT.Players.Computer, pos);
       }
   },


   lookForWin: function(){
       //horizontal
       var i=0;
       for(i=0; i<9; i=i+3){
           this.hasWinner = this.matchMovePositions(i,i+1,i+2);
           if(this.hasWinner){
               this.winningCells = [i,i+1,i+2]
               return;
           }
       }

       //vertical
       for(i=0; i<3; i++){
           this.hasWinner = this.matchMovePositions(i,i+3,i+6);
           if(this.hasWinner){
               this.winningCells = [i,i+3,i+6]
               return;
           }
       }

       //diag
       if(!this.hasWinner){
           this.hasWinner = this.matchMovePositions(0,4,8);
           if(this.hasWinner){
               this.winningCells = [0,4,8]
           }
       }
       if(!this.hasWinner){
           this.hasWinner = this.matchMovePositions(2,4,6);
           if(this.hasWinner){
               this.winningCells = [2,4,6]
           }
       }

   },

   /**
    * Checks to see if the current game is a draw (all cells filled)
	*
	*/
   isADraw : function(){
		var openCell = false;
		for(var i=0; i < 9; i++){
			if(!this.moves[i]){
				openCell = true;
				break;
			}
		}
		return (!openCell);
   },


   /**
    * Tests three positions to see if they match
    * @param {Object} a
    * @param {Object} b
    * @param {Object} c
    */
   matchMovePositions : function(a, b, c){
       if(this.moves[a]){
           return (this.moves[a] == this.moves[b] && this.moves[a] == this.moves[c]);
       }
       return false;
   }
};

TTT.CustomBackground = function(){
   this._hasCustomBG = false;
   this.photo = {};
   this.cellbackCustomBG = "";

   /**
    * Returns true/false based on if a custom user background is
    * currently in use
    */
   this.usingCustomBG = function(){
      return this._hasCustomBG;
   };

   /**
    * Gets the background of the TTT board
    */
   this.getCustomBG = function(){
      var start = currentGame.displayBoardElem.style.background.indexOf("http");
      var end = currentGame.displayBoardElem.style.background.indexOf(")");

      return currentGame.displayBoardElem.style.background.substring(start, end);
   };

   /**
    * Sets the background of the TTT board to a custom user image
    */
   this.setCustomBG = function(photo){
      this.photo = photo;
      var img_src = photo.getField(MyOpenSpace.Photo.Field.IMAGE_URI);

      currentGame.displayBoardElem.style.background = "url(" + img_src + ")";
      this._hasCustomBG = true;

      for(i = 0; i < currentGame.gameCells.length; i++){
         currentGame.gameCells[i].style.backgroundColor = this.cellbackCustomBG;
      }
   };
}

TTT.LightBox = {
   _overlay:null,
   _light_box:null,
   _light_box_container:null,
   _is_init:false,

   _init:function(){
      this._overlay = document.getElementById("overlay");
      this._light_box = document.getElementById("light_box");
      this._light_box_container = document.getElementById("light_box_container");
      this._is_init = true;
   },

   show:function(){
      if(!this._is_init){
         this._init();
      }

      this._overlay.style.display = "block";
      this._light_box_container.style.display = "block";
   },
   hide:function(){
      this._overlay.style.display = "none";
      this._light_box_container.style.display = "none";
   },
   setContent:function(html){
      if(!this._is_init){
         this._init();
      }

      this._light_box.innerHTML = html;
   }
};

TTT.Tab = function(tab_dom, container, action){
   this.tab_dom = tab_dom;
   this.container = container;

   if(this.container){
      this.container.hide = function(){
         this.style.display = "none";
      }

      this.container.show = function(){
         this.style.display = "block";
      }
   }

   this.action = action;
};

TTT.Tabs = {
   _tabs:[],

   _selectedTab:null,

   _initTabs:function(){
      this._tabs[0] = new TTT.Tab(document.getElementById("tab0"), document.getElementById("play_container"), playClicked);
      this._tabs[1] = new TTT.Tab(document.getElementById("tab1"), document.getElementById("invite_container"), inviteClicked);
      this._tabs[2] = new TTT.Tab(document.getElementById("tab2"), document.getElementById("challenge_container"), challengeClicked);
      this._tabs[3] = new TTT.Tab(document.getElementById("tab3"), document.getElementById("set_background_container"), setBackgroundClicked);
   },

   getCurrentContainer:function(){
      return this._tabs[this._selectedTab].container;
   },

   getCurrentTab:function(){
      return this._tabs[this._selectedTab];
   },

   selectTab:function(index){
      if(0 === this._tabs.length){
         this._initTabs();
      }

      if(index !== this._selectedTab){
         this._tabs[index].tab_dom.className += " tab_selected";

         var selected = this._tabs[this._selectedTab];
         if(null !== this._selectedTab){
            var ind = selected.tab_dom.className.indexOf(" tab_selected");
            selected.tab_dom.className = selected.tab_dom.className.substr(0, ind);

            selected.container.hide();
         }
         this._selectedTab = index;

         this._tabs[index].container.show();
         this._tabs[index].action();
      }
   }
};

TTT.List = function(list_type, id_key, name_key, image_key, class_name, item_onclick){
   this.list_type = list_type;
   this.list = [];
   this.list_dom = {};
   this.first = 1;
   this.max = 20;
   this.total = 0;
   this.retries = 3;

   this.id_key = id_key;
   this.name_key = name_key;
   this.image_key = image_key;
   this.class_name = class_name;
   this.item_onclick = item_onclick;
};

TTT.ListTypes = {
   ALL_FRIENDS:"ALL_FRIENDS",
   HASAPP_FRIENDS:"HASAPP_FRIENDS",
   SET_BACKGROUND:"SET_BACKGROUND"
};

TTT.Lists = {
   _current:"",

   itemClicked:[],

   getCurrentList:function(){
      for(var key in this.lists){
         if(this.lists[key].list_type === this._current){
            return this.lists[key];
         }
      }
   },

   setCurrentList:function(list_type){
      this._current = list_type;
   },

   lists:{
      all_friends:new TTT.List(TTT.ListTypes.ALL_FRIENDS, opensocial.Person.Field.ID, opensocial.Person.Field.NICKNAME, opensocial.Person.Field.THUMBNAIL_URL, "friend", rsaInviteClicked),
      hasapp_friends:new TTT.List(TTT.ListTypes.HASAPP_FRIENDS, opensocial.Person.Field.ID, opensocial.Person.Field.NICKNAME, opensocial.Person.Field.THUMBNAIL_URL, "friend", rsmMessage),
      viewer_photos:new TTT.List(TTT.ListTypes.SET_BACKGROUND, MyOpenSpace.Photo.Field.PHOTO_ID, MyOpenSpace.Photo.Field.CAPTION, MyOpenSpace.Photo.Field.IMAGE_URI, "photo", photoClicked)
   },

   markup_format:"<div><img src='{0}' /></div>{1}",

   permCallback:function(response){
      if(!response.hadError()){
         TTT.Lists.retryRequest();
      }
   },

   retryRequest:function(){
      TTT.Lists.getCurrentList().retries--;
      window.setTimeout(TTT.Tabs.getCurrentTab().action, 250);
   }
};

TTT.Lists.callback = function(response){
   var current_list = TTT.Lists.getCurrentList();

   var retryRequest = function(){
      current_list.retries--;
      window.setTimeout(fetchFriends, 250);
   };

   // check for an error
   if(!response.hadError()){
      var data = null;

      // loop through the available keys to
      // find the actual data in the response
      for(var key in TTT.RequestKeys){
         if(response.get(TTT.RequestKeys[key])){
            data = response.get(TTT.RequestKeys[key]).getData();
            break;
         }
      }

      // something bad happened to it
      if(null === data){
         retryRequest();
         return;
      }
      else{
         // save the actual data to the TTT.List object
         current_list.list = data.asArray();

         // save the total number of items in the list
         current_list.total = data.getTotalSize();

         // draw the list
         TTT.Lists.draw();
      }
   }
   else{
      // if there was a permission issue
      if(data.getErrorCode() === opensocial.ResponseItem.Error.UNAUTHORIZED){
         var reason = "To set a custom background for the game board!";
         opensocial.requestPermission([MyOpenSpace.Permission.VIEWER_ACCESS_TO_PUBLIC_VIDEOS_PHOTOS], reason, TTT.Lists.permCallback);
      }
      // if there was an error and there are retries left, go for it
      else if(current_list.retries > 0){
         retryRequest();
         return;
      }
      // out of retries...
      else{
         log("Oops, there was an error! Try refreshing the page.");
      }
   }
};

TTT.Lists.draw = function(){
   var div, id, name, picture;

   // create a container to insert all the items into
   var all_items = document.createElement("div");

   var current_list = this.getCurrentList();

   // display a message if the list is empty
   if(0 === current_list.list.length){
      TTT.Tabs.getCurrentContainer().innerHTML = "Oops, no results to show!";
      return;
   }

   // add the pager markup
   all_items.innerHTML = TTT.Lists.Pager.getMarkUp();

   for(var i = 0; i < current_list.list.length; i++){
      id = current_list.list[i].getField(current_list.id_key);
      name = current_list.list[i].getField(current_list.name_key);
      picture = current_list.list[i].getField(current_list.image_key);

      // create the item's div element
      div = document.createElement("div");
      div.className = current_list.class_name;
      div.onclick = current_list.item_onclick;
      div.list_index = id;
      div.innerHTML = this.markup_format.replace("{0}", picture).replace("{1}", name);

      // append it to the container
      all_items.appendChild(div);

      // save a reference to the element
      current_list.list_dom[id] = div;
   }

   // add a clearing div since we're using css floats
   div = document.createElement("div");
   div.className = "clear";
   all_items.appendChild(div);

   var save_first_child = TTT.Tabs.getCurrentContainer().firstChild.innerHTML;
   TTT.Tabs.getCurrentContainer().innerHTML = "<div>" + save_first_child + "</div>";

   // add all the items at once to the current tab, doing it all
   // at once means that we only update the DOM once
   TTT.Tabs.getCurrentContainer().appendChild(all_items);
};

TTT.Lists.Pager = {
   pager_wrap:"<div class='pager'>{0}</div>",
   pager_prev:"<span onclick='TTT.Lists.Pager.prev();'><< prev</span>&nbsp;",
   pager_next:"&nbsp;<span onclick='TTT.Lists.Pager.next();'>next >></span>",
   page_size:20,

   getMarkUp:function(){
      var current_list = TTT.Lists.getCurrentList();

      if(current_list.list.length < this.page_size &&
               1 === current_list.first){
         // if the length is less than the amount requested
         // there is only one page, no need for a pager
         return "";
      }

      var markup = "";

      if(current_list.first > this.page_size){
         // if the first index is greater than the size of
         // a page then there will be a previous page
         markup += this.pager_prev;
      }

      if(current_list.total > current_list.first + current_list.list.length - 1){
         // if the total number of records is greater than
         // the current index, plus the amount in the current
         // result set, minus 1, then there's another page.
         // e.g. first = 1, current set = 20, total = 55
         // 1 + 20 - 1 = 20 records fetched, < 55
         // e.g. first = 21, current set = 20, total = 55
         // 21 + 20 - 1 = 40 records fetched, < 55
         // e.g. first = 41, current set = 10, total = 55
         // 41 + 15 - 1 = 55 records fetched, == 55
         markup += this.pager_next;
      }

      if(markup.length > 0){
         markup = this.pager_wrap.replace("{0}", markup);
      }

      return markup;
   },
   prev:function(){
      var current_list = TTT.Lists.getCurrentList();

      current_list.first -= this.page_size;
      fetchFriends(current_list.list_type);
   },
   next:function(){
      var current_list = TTT.Lists.getCurrentList();

      current_list.first += this.page_size;
      fetchFriends(current_list.list_type);
   }
};



/**
 * Set a new persistent cookie (or overwrite existing)
 * @param {Object} key
 * @param {Object} value
 */
function setCookie(key, value){
	var cook = key + '=' + value + '; expires=1 Jan 2050; path=/';
	document.cookie=cook;
}

/**
 * Read the given cookie
 * @param {Object} key
 */
function readCookie(key){
	var cookies = document.cookie;
	var keyName = key + '=';
	var pos = cookies.indexOf(keyName);
	if(pos > -1){
		pos += keyName.length;
		var endPos = cookies.indexOf(';', pos);
		if(-1 == endPos){
			endPos = cookies.length;
		}
		return cookies.substring(pos, endPos);
	}
	return null;
}




var currentSetCallback = null;


var currentGame = null;

function makePlayerMove(e){
   if(!e) var e = window.event;
   var cell = (e.target) ? e.target.getAttribute("gamecell") : e.srcElement.getAttribute("gamecell");

	if(currentGame.isHumanOpponent && (currentGame.currentPlayer == TTT.Players.Computer)){
		TTT.AppDataPlay.updateGameStatusMessage("YO!  I said you have to wait your turn.");
		return;
	}


   currentGame.makeMove(TTT.Players.Human, cell);
   if(!currentGame.hasWinner && !currentGame.isHumanOpponent){
       window.setTimeout(function(){
           currentGame.makeComputerMove();
       }, 500);
   }
   else{

		var quotes = ["Nice Move!", "...interesting strategy", "What were you thinking?"];
		var d = new Date();
		var ndx = d.getMilliseconds() % 3;
		var msg = quotes[ndx]; //random quote
		var adGame = TTT.AppDataPlay.getCurrentGameObject(TTT.AppDataPlay.currentOpponentId);
		adGame.moveNumber = adGame.moveNumber+1;

		if(currentGame.hasWinner){
			msg = "You Win!";
			adGame.winner = window.viewer.getId();
		}
		else{
			msg += "<br />Waiting for your opponent";
		}

		adGame.boardLayout = TTT.AppDataPlay.getGameMovesString(currentGame);
		adGame.currentPlayer = TTT.AppDataPlay.currentOpponentId;
		if(currentGame.isADraw()){
			msg += "<br />IT'S A DRAW!";
			adGame.winner = -1;
		}
		else if(!currentGame.hasWinner && !currentGame.isADraw()){
			TTT.AppDataPlay.startGameplayPolling();
		}
		TTT.AppDataPlay.updateStoredGameObject();

		TTT.AppDataPlay.updateGameStatusMessage(msg);

	}

}



function getOtherNameData(){
	//debugger;
	var fld = document.getElementById("txtOtherUserID");
	var id = fld.value;

	var req = opensocial.newDataRequest();
	var fields = [ TTT.Keys.NAME, TTT.Keys.SCORE ];
	req.add(req.newFetchPersonAppDataRequest(opensocial.DataRequest.PersonId.VIEWER, fields), TTT.RequestKeys.VIEWERDATA);
	if(viewer == null){
		req.add(req.newFetchPersonRequest(id), "myotherkey");
	}

	req.send(getOtherNameDataCallback);

}

function getOtherNameDataCallback(data){
	if (data.hadError()) {
		return; //exit if nothing found
	}
	var x = data.get("myotherkey");
	var mydata = x.getData();
	//app data has an additional layer of indirection
	//we reassign mydata object to the data map object
	//circumvent viewer lookup by getting key
	var ok = false;
	for(var vkey in mydata){
		mydata = mydata[vkey];
		ok = true;
		break;
	}
	if(ok){
		var key
		for(key in mydata){
			myLocalAppData[key] = mydata[key];
		}
	}
}




/**
 * Sets the persistent value for the game in the
 * chosen data store
 * @param {String} key
 * @param {String} value
 */
function setGameData(key, value){
	if(useCookieStorage){
		setCookie(key, value);
		getInitialData()
	}
	else{
		setAppDataKey(key, value);
	}
}


function setAppDataKey(key, value) {
  currentSetCallback = getInitialData;

  var req = opensocial.newDataRequest();
  req.add(req.newUpdatePersonAppDataRequest(opensocial.IdSpec.PersonId.VIEWER, key, value), "set_" + key);
  req.send(setDataCallback);
};


var myLocalAppData = {}; //empty object hash for local appdata
var appdataKey = "mydata"; //key to use in dataRequest for retrieval

/**
 * Loads some or all of the AppData into a local store variable myLocalAppData
 * @param {Array} fields  Array of fields to retrieve, or null for all fields
 * @param {Function} loadedCallback  Callback handler to fire when App Data has loaded.
 */
function loadAppData(fields, loadedCallback){
	var req = opensocial.newDataRequest();
	if(fields == null){
		fields = "*"; //default to all data
	}
	req.add(req.newFetchPersonAppDataRequest(opensocial.IdSpec.PersonId.VIEWER, fields), appdataKey);
	req.send(function(data){
debugger;
		loadAppDataCallback(data, loadedCallback)
	});
}

/**
 * Callback wrapper method for loadAppData.
 * This loads all the data into myLocalAppData
 * and triggers the loadedCallback function, if specified
 * @param {Object} data
 * @param {Function} loadedCallback
 */
function loadAppDataCallback(data, loadedCallback){
	logDataError(data);
	if (data.hadError()) {
		return; //exit if nothing found
	}
	var mydata = data.get(appdataKey).getData();
	//app data has an additional layer of indirection
	//we reassign mydata object to the data map object
	//circumvent viewer lookup by getting key
	var ok = false;
	for(var vkey in mydata){
		mydata = mydata[vkey];
		ok = true;
		break;
	}
	if(ok){
		var key
		for(key in mydata){
			myLocalAppData[key] = mydata[key];
		}
	}

	if(loadedCallback && typeof(loadedCallback) == "function"){
		loadedCallback(myLocalAppData);
	}
}


function setDataCallback(data){
	logDataError(data);
	if(currentSetCallback != null){
		currentSetCallback(data);
		currentSetCallback = null;
	}
}

function setNameData(){
	var elem = document.getElementById("txtName");
	if(elem){
	  var jval = gadgets.json.stringify(elem.value);

		setGameData(TTT.Keys.NAME, jval);
	}
}




/**
 * Logs a data request error to the messages div
 * @param {Object} data
 */
function logDataError(data){
	if (data.hadError()) {
		//hack to discover keys
		if(data.responseItems_){
			var key;
			var curData;
			for (key in data.responseItems_){
				curData = data.get(key);
				if(curData.hadError()){
					log(curData.getErrorMessage());
				}
			}
		}
	}
}

function log(message){
	var errFeed = document.getElementById("messages");
	if(errFeed){
		errFeed.innerHTML += message + "<br />";
	}
}

/**
 * Write the name value and score value to the myinfo div
 * @param {Object} name
 * @param {Object} score
 */
function writeNameAndScore(name, score){
	var elem = document.getElementById("myinfo");
	if(elem){
		var str = "<h1>";
		if(!name){
			if(viewer){
				str += viewer.getDisplayName();
			}
		}
		else{
			str += name;
		}
		str += "<br />\nScore: <span style='color:red;'>";
		if(!score){
			str += "[no games]";
		}
		else{
			str += score;
		}
		str += "</span></h1>";
		elem.innerHTML =  str;
	}
}



/**
 * Data initializer to load stored information
 */
function getInitialData(){

	if(useCookieStorage){
		if(viewer == null){
			getViewerData();
		}
		writeNameAndScore(readCookie(TTT.Keys.NAME), readCookie(TTT.Keys.SCORE));
	}
	else{
		if(getAppDataV1){
			getInitialAppDataV1();
		}
		else{
			getInitialDataV2();
		}
	}
}

/**
 * Get the discrete app data and initialize the view
 */
function getInitialAppDataV1(){
	var req = opensocial.newDataRequest();
	var fields = [ TTT.Keys.NAME, TTT.Keys.SCORE ];
	req.add(req.newFetchPersonAppDataRequest(opensocial.DataRequest.PersonId.VIEWER, fields), TTT.RequestKeys.VIEWERDATA);
	if(viewer == null){
		req.add(req.newFetchPersonRequest(opensocial.DataRequest.PersonId.VIEWER), TTT.RequestKeys.VIEWER);
	}

	req.send(getDataCallbackV1);
}


/**
 * Refactored load of initial data.  This utilizes the local store
 */
function getInitialDataV2(){
	if(viewer == null){
		getViewerData();
	}
	loadAppData(null, getDataCallbackV2);
}

/**
 * Callback handler for getting AppData and viewer
 * @param {Object} data
 */
function getDataCallbackV1(data){
	logDataError(data);
	var mydata = data.get(TTT.RequestKeys.VIEWERDATA).getData();
	if(!viewer){
		viewer = data.get(TTT.RequestKeys.VIEWER).getData();
	}
	//this happens if app not installed
	if(!viewer){
		log("<b>APP NOT INSTALLED!</b>")
		return;
	}
	else{
		mydata = mydata[viewer.getId()];
		writeNameAndScore(mydata[TTT.Keys.NAME], mydata[TTT.Keys.SCORE]);
	}
}

/**
 * Loads the data from a hash object passed in
 * @param {Object} localAppData
 */
function getDataCallbackV2(localAppData){
	if (viewer == null) {
		getViewerData();
	}

	var nameJson = gadgets.json.parse(localAppData[TTT.Keys.NAME]);

	writeNameAndScore(localAppData[TTT.Keys.NAME], localAppData[TTT.Keys.SCORE]);
}



/**
 * Get just the viewer data from a personRequest
 */
function getViewerData(){
	var req = opensocial.newDataRequest();
	req.add(req.newFetchPersonRequest(opensocial.DataRequest.PersonId.VIEWER), TTT.RequestKeys.VIEWER);
	req.send(getViewerCallback);
}


/**
 * Callback handler to initialize the viewer object from a datarequest
 * @param {Object} data
 */
function getViewerCallback(data){
	logDataError(data);
	viewer = data.get(TTT.RequestKeys.VIEWER).getData();

	if(!viewer){
		log("<b>APP NOT INSTALLED!</b>")
		return;
	}
}





/**
 * Function called by the onload handler and the entry point for the app
 */
function initializeGame(){
   getInitialData();

   if(currentGame){
      currentGame.clear();
   }
   else{
      gadgets.window.adjustHeight(3000);
      currentGame = new TTT.Game();
   }
}


function opponentPickedAction(person){
	TTT.AppDataPlay.selectOpponent(person.getId());

	var elem = document.getElementById("availableGames");
	elem.innerHTML = person.getDisplayName();
}
function loadFriendPicker(){
  MyOpenSpace.Widgets.Bootstrapper.createWidget(
    "MyOpenSpace.Widgets.FriendPicker",
  function(p){
      window.friendPicker = p;
	p.pageSize= 3
  },
  {
    element: "opponentPicker",
    buildSelectedUI: true,
    friendClickAction: opponentPickedAction
  });
}




var retries = 3;
var debug = true;

/**
 * Makes the initial call for the viewer's opensocial.Person object and requests all
 * fields supported by MySpace
 */
function getInitialData(){

   // returns an opensocial.DataRequest object
   var req = opensocial.newDataRequest();

   if(null === viewer){

      // used to specify what data is returned in the response
      var fields = [opensocial.Person.Field.ABOUT_ME, // string
                     opensocial.Person.Field.AGE, // number
                     opensocial.Person.Field.BODY_TYPE, // opensocial.BodyType
                     opensocial.Person.Field.BOOKS, // array of strings
                     opensocial.Person.Field.CHILDREN, // string
                     opensocial.Person.Field.CURRENT_LOCATION, // opensocial.Address
                     opensocial.Person.Field.DATE_OF_BIRTH, // Date
                     opensocial.Person.Field.DRINKER, // opensocial.Enum.Drinker
                     opensocial.Person.Field.ETHNICITY, // string
                     opensocial.Person.Field.GENDER, // opensocial.Enum.Gender
                     opensocial.Person.Field.HAS_APP, // boolean
                     opensocial.Person.Field.HEROES, // array of strings
                     opensocial.Person.Field.ID, // string
                     opensocial.Person.Field.INTERESTS, // array of strings
                     opensocial.Person.Field.JOBS, // array of opensocial.Organization
                     opensocial.Person.Field.LOOKING_FOR, // opensocial.Enum.LookingFor
                     opensocial.Person.Field.MOVIES, // array of strings
                     opensocial.Person.Field.MUSIC, // array of strings
                     opensocial.Person.Field.NAME, // opensocial.Name
                     opensocial.Person.Field.NETWORK_PRESENCE, // opensocial.Enum.Presence
                     opensocial.Person.Field.NICKNAME, // strings
                     opensocial.Person.Field.PROFILE_SONG, // opensocial.Url
                     opensocial.Person.Field.PROFILE_URL, // strings
                     opensocial.Person.Field.RELATIONSHIP_STATUS, // string
                     opensocial.Person.Field.RELIGION, // strings
                     opensocial.Person.Field.SEXUAL_ORIENTATION, // strings
                     opensocial.Person.Field.SMOKER, // opensocial.Enum.Smoker
                     opensocial.Person.Field.STATUS, // string
                     opensocial.Person.Field.THUMBNAIL_URL, // string
                     opensocial.Person.Field.TV_SHOWS, // array of strings
                     opensocial.Person.Field.URLS, // array of opensocial.Url
                     MyOpenSpace.Person.Field.MEDIUM_IMAGE, // opensocial.Url
                     MyOpenSpace.Person.Field.LARGE_IMAGE // opensocial.Url
                     ];

      // create an empty object to use for passing in the parameters,
      // the parameter here are addition Person fields from above
      var opt_params = {};

      // add the list of fields to the parameters
      opt_params[opensocial.DataRequest.PeopleRequestFields.PROFILE_DETAILS] = fields;

      // create a reqiest object, passing in the viewer, this will
      // specify we want the opensocial.Person for the viewer
      var viewer_req = req.newFetchPersonRequest(opensocial.IdSpec.PersonId.VIEWER, opt_params);

      // add the request to the queue and give it a key
      req.add(viewer_req, "v");
   }

   // start processing the requests and specify the callback function
   req.send(getDataCallback);
}

var viewer = null;

/**
 * The callback method for the initial request
 * @param {opensocial.DataResponse} data
 */
function getDataCallback(data){

   // first thing's first... always check if there was an error with your request
   if(!requestHadError(data)){

      // "v" was the key created in the request,
      // data is an object of type opensocial.DataResponse
      // data.get("v") returns an opensocial.ResponseItem
      // data.get("v").getData() will return the actual data,
      // in this case an opensocial.Person object
      viewer = data.get("v").getData();

      // now let's do something with all that data
      printPerson(document.getElementById("myinfo"));

      // unless the height of your app is fixed it's usually a good idea to call
      // gadgets.window.adjustHeight after you output content into the DOM as
      // some content could get cut off

      // this was removed in chapter 3, instead the app is resized to a
      // very large size right at the beginning

      //gadgets.window.adjustHeight();
   }
}

/**
 * Check if the response had an error, if it did, log it and if it was an INTERNAL_ERROR attempt to retry the request
 * @param {opensocial.DataResponse} data
 */
function requestHadError(data){

   // check the opensocial.DataResponse for the global error flag
   if(!data || data.hadError()){

      // find the specific opensocial.ResponseItem that had the error
      var ri = data.get(TTT.RequestKeys.VIEWER);

      if(ri && ri.hadError()){

         // output the error message
         log(ri.getErrorMessage());

         // check the error code, an INTERNAL_ERROR can simply mean network congestion or MySpace server instability
         if(opensocial.ResponseItem.Error.INTERNAL_ERROR === ri.getErrorCode()){

            // retry the request a certain number of times, make sure you don't create an infinite loop here!
            if(retries > 0){
               retries--;
               window.setTimeout(getInitialData, 1000);
            }
         }
      }

      return true;
   }

   return false;
}

/**
 * Output the viewer data onto the surface
 */
function printPerson(div){
   if(null !== viewer){

      var base_obj = [], num_set = 0;
      for(var i = 0; i < div.childNodes.length; i++){
         if(1 === div.childNodes[i].nodeType){
            base_obj[num_set] = div.childNodes[i];
            if(1 === num_set){
               break;
            }
            else{
               num_set++;
            }
         }
      }

      var img_div = base_obj[0].firstChild.firstChild;
      var name_div = base_obj[1].firstChild;
      var location_div = base_obj[1].childNodes[1];
      var gender_div = base_obj[1].childNodes[2];

      // you can set the src attribute of an <img> tag directly with THUMBNAIL_URL
      img_div.src = viewer.getField(opensocial.Person.Field.THUMBNAIL_URL);

      // getDisplayName is a shortcut for getField(opensocial.Person.Field.NICKNAME)
      name_div.innerHTML = viewer.getDisplayName();

      // get the viewer's status
      var status = viewer.getField(opensocial.Person.Field.STATUS);
      if(status && status.length > 0){

         // if the status has been set append it after the name
         name_div.innerHTML += " \"" + status + "\"";
      }

      // get the opensocial.Address object
      var location = viewer.getField(opensocial.Person.Field.CURRENT_LOCATION);

      // the Address object is used similarly to the Person object, both pass a
      // Field into a getField function
      location_div.innerHTML = location.getField(opensocial.Address.Field.REGION);

      // gender is an opensocial.Enum object
      var gender = viewer.getField(opensocial.Person.Field.GENDER);

      // getDisplayValue is defined by the specific container and can and
      // will differ between containers and is designed for displaying only
      gender_div.innerHTML = gender.getDisplayValue();

      // the response of getKey is defined by OpenSocial and therefore
      // you can compare the result to some known value
      if(gender.getKey() === opensocial.Enum.Gender.FEMALE){
         div.style.backgroundColor = "#fcf";
      }
      else{
         div.style.backgroundColor = "#09f";
      }

      /**
       * Takes an opensocial.Person.Field or MyOpenSpace.Person.Field, parses the data
       * out from the viewer object and formats the output to something that can be
       * displayed on the surface
       * @param {*.Person.Field} field
       * @param {boolean} add_break
       */
      var parsePersonField = function(field, add_break){
         var str = "";

         // get whatever data is stored in the viewer for the field, can be undefined,
         // a string, number, object, array or opensocial.Enum
         var val = viewer.getField(field);

         if(!val) return "";

         switch(field){
            case opensocial.Person.Field.ABOUT_ME:
            case opensocial.Person.Field.CHILDREN:
            case opensocial.Person.Field.ETHNICITY:
            case opensocial.Person.Field.ID:
            case opensocial.Person.Field.NICKNAME:
            case opensocial.Person.Field.PROFILE_URL:
            case opensocial.Person.Field.RELATIONSHIP_STATUS:
            case opensocial.Person.Field.RELIGION:
            case opensocial.Person.Field.SEXUAL_ORIENTATION:
            case opensocial.Person.Field.STATUS:
            case opensocial.Person.Field.THUMBNAIL_URL:
               str = val; // these are all the strings and are easiest to parse
               break;
            case opensocial.Person.Field.AGE:
               str = val + " years old"; // this is a number
               break;
            case opensocial.Person.Field.BODY_TYPE:
               // opensocial.BodyType, MySpace only returns BUILD and HEIGHT
               if(val.getField(opensocial.BodyType.Field.BUILD)){
                  str = "Build: " + val.getField(opensocial.BodyType.Field.BUILD);
               }
               if(val.getField(opensocial.BodyType.Field.HEIGHT)){
                  if(str !== ""){
                     str += "<br />";
                  }
                  str = "Height: " + val.getField(opensocial.BodyType.Field.HEIGHT);
               }
               break;
            case opensocial.Person.Field.BOOKS:
            case opensocial.Person.Field.HEROES:
            case opensocial.Person.Field.INTERESTS:
            case opensocial.Person.Field.MOVIES:
            case opensocial.Person.Field.MUSIC:
            case opensocial.Person.Field.TV_SHOWS:
               // array of strings, these will all be array of size 1 as these are free
               // form text fields in a MySpace profile, so you'll get a big blob of
               // unparseable text for these
               if(val.length > 0){
                  str = val[0];
               }
               break;
            case opensocial.Person.Field.CURRENT_LOCATION:
               // opensocial.Address, MySpace only returns REGION, COUNTRY and POSTAL_CODE
               str = val.getField(opensocial.Address.Field.REGION) + " ";
               str += val.getField(opensocial.Address.Field.COUNTRY) + " ";
               str += val.getField(opensocial.Address.Field.POSTAL_CODE);
               break;
            case opensocial.Person.Field.DATE_OF_BIRTH:
               str = "Born: " + val.toDateString(); // Date
               break;
            case opensocial.Person.Field.DRINKER:
            case opensocial.Person.Field.GENDER:
            case opensocial.Person.Field.LOOKING_FOR:
            case opensocial.Person.Field.NETWORK_PRESENCE:
            case opensocial.Person.Field.SMOKER:
               str = val.getDisplayValue(); // opensocial.Enum
               break;
            case opensocial.Person.Field.HAS_APP:
               str = (val) ? "Has the app installed" : "App not installed"; // boolean
               break;
            case opensocial.Person.Field.JOBS:
               // array of opensocial.Organization, MySpace only returns NAME and TITLE
               for(var i = 0; i < val.length; i++){
                  str += "Name: " + val[i].getField(opensocial.Organization.Field.NAME);
                  if(val[i].getField(opensocial.Organization.Field.TITLE)){
                     str += "<br />" + val[i].getField(opensocial.Organization.Field.TITLE) + "<br />";
                  }
               }
               break;
            case opensocial.Person.Field.NAME:
               // opensocial.Name, MySpace only returns UNSTRUCTURED
               str = val.getField(opensocial.Name.Field.UNSTRUCTURED);
               break;
            case opensocial.Person.Field.PROFILE_SONG:
            case MyOpenSpace.Person.Field.MEDIUM_IMAGE:
            case MyOpenSpace.Person.Field.LARGE_IMAGE:
               // opensocial.Url, MySpace returns ADDRESS and TYPE
               str = val.getField(opensocial.Url.Field.ADDRESS);
               break;
            case opensocial.Person.Field.URLS:
               // array of opensocial.Url, will only return the profile URL, so it's
               // better to use opensocial.Person.Field.PROFILE_URL here instead
               for(var i = 0; i < val.length; i++){
                  str += val[i].getField(opensocial.Url.Field.ADDRESS);
               }
               break;
            default:
               break;
         }

         if(add_break) str += "<br />";

         return str;
      }

      // MySpace provides a built in StringBuilder class which can take an initial string in the constructor,
      // and has append and toString functions, this can be easier to use in some cases than simple
      // concatenation
      var sb = new MyOpenSpace.StringBuilder("<div id='player_bio'><div id='player_bio_title'>Player Bio</div>");
      sb.append("<div style='margin:5px;'><div class='left' style='height:300px;'><img class='profile_image' src='");

      sb.append(parsePersonField(MyOpenSpace.Person.Field.MEDIUM_IMAGE, false));
      sb.append("' /></div><div>");

      var status = parsePersonField(opensocial.Person.Field.STATUS, true);
      if("" !== status){
         sb.append("<b>");
         sb.append(status);
         sb.append("</b>");
      }

      sb.append(parsePersonField(opensocial.Person.Field.NAME, true));
      sb.append(parsePersonField(opensocial.Person.Field.ID, true));
      sb.append(parsePersonField(opensocial.Person.Field.DATE_OF_BIRTH, true));
      sb.append(parsePersonField(opensocial.Person.Field.AGE, true));
      sb.append(parsePersonField(opensocial.Person.Field.CURRENT_LOCATION, true));
      sb.append(parsePersonField(opensocial.Person.Field.BODY_TYPE, true));
      sb.append(parsePersonField(opensocial.Person.Field.CHILDREN, true));

      var drinks = parsePersonField(opensocial.Person.Field.DRINKER, true);
      if("" !== drinks){
         sb.append("Drinks: " + drinks);
      }

      var smokes = parsePersonField(opensocial.Person.Field.SMOKER, true);
      if("" !== smokes){
         sb.append("Smokes: " + smokes);
      }

      sb.append(parsePersonField(opensocial.Person.Field.ETHNICITY, true));
      sb.append(parsePersonField(opensocial.Person.Field.GENDER, true));
      sb.append(parsePersonField(opensocial.Person.Field.HAS_APP, true));
      sb.append(parsePersonField(opensocial.Person.Field.NETWORK_PRESENCE, true));
      sb.append(parsePersonField(opensocial.Person.Field.RELATIONSHIP_STATUS, true));

      sb.append("</div></div><div class='clear'></div>");
      sb.append("<div id='bio_close'><a href='javascript:TTT.LightBox.hide();'>close...</a></div>");
      sb.append("</div>");

      TTT.LightBox.setContent(sb.toString());
   }
}

function openProfile(){
   // profile URL is a simple string that can go directly into the src
   // attribute of an <img> tag, or, like here, directly into window.open()
   window.open(viewer.getField(opensocial.Person.Field.PROFILE_URL));
}

/**
 * Output an error message if debug mode is enabled
 * @param {String} message
 */
function log(message){
   var errFeed = document.getElementById("messages");
   if(errFeed){
      errFeed.innerHTML += message + "<br />";
   }
}

function rsmWrapper(id, subject, body, type, callback){
   var param = {};
   param[opensocial.Message.Field.TYPE] = type;
   param[opensocial.Message.Field.TITLE] = subject;

   var message = opensocial.newMessage(body, param);

   opensocial.requestSendMessage(id, message, callback);
}

function raiseActivity(){
   // create the parameters
   var param = {};

   // required template name
   param[opensocial.Activity.Field.TITLE_ID] = "x_and_y_started_game";

   // the actual parameter values
   param[opensocial.Activity.Field.TEMPLATE_PARAMS] = {
      "opponent" : currentGame.opponentId,
      "params" : "{\"from\":\"act\"}"
   };

   // check if a custom background is set to use for
   // a media item
   if(currentGame.customBG.usingCustomBG()){
      // get the photo object
      var photo = currentGame.customBG.photo;

      // parse out the URI
      var uri = photo.getField(MyOpenSpace.Photo.Field.PHOTO_URI);

      // create the opensocial.MediaItem object
      var media_item = opensocial.newMediaItem("image/jpeg", uri)

      // stick it in an array
      var media_item_array = [ media_item ];

      // insert the array into the parameters
      param[opensocial.Activity.Field.MEDIA_ITEMS] = media_item_array;
   }

   // create the opensocial.Activity object
   var activity = opensocial.newActivity(param);

   // raise the activity!
   opensocial.requestCreateActivity(activity,
         opensocial.CreateActivityPriority.HIGH, raiseActivityCallback);
}

function raiseActivityCallback(response) {
   // check for an error
   if(response.hadError()){

      // was the error a permission issue?
      if(response.getErrorCode() ===
            opensocial.ResponseItem.Error.UNAUTHORIZED){

         // beg for permission
         var reason = "To inform your friends of your upcoming match!";

         // pick which permission
         // STICK IT IN AN ARRAY!!
         var perm = [MyOpenSpace.Permission.VIEWER_SEND_UPDATES_TO_FRIENDS];

         // pretty please...
         opensocial.requestPermission(perm, reason, actPermCallback);
      }
      else{
         // some other error
         log("Oops, there was an error, try refreshing!");
      }
   }
}

function actPermCallback(response){
   // was permission granted?
   if(!response.hadError()){
      // yay!
      raiseActivity();
   }
}

// version using the default template
function rsmNotification(recipient, game_id){
   // setup all the data we'll need
   var body = "${sender} has finished their move ";
   body += "in ${app}, it's your turn!";
   var url = MyOpenSpace.NotificationButton.UrlTypes.CANVAS;
   var button_params = {
      "from" : "noti",
      "action" : "play",
      "game_id" : game_id
   };
   var button_text = "Play Now!";

   var param = {};

   // create a MyOpenSpace.NotificationButton object
   var button1 = MyOpenSpace.newNotificationButton(url,
         button_text, button_params);

   var param = {};

   param[MyOpenSpace.Notification.Field.BODY] = body;
   param[MyOpenSpace.Notification.Field.BUTTONS] = [button1];

   // create the MyOpenSpace.Notification object
   var notification = MyOpenSpace.newNotification(param);

   // send it off!
   MyOpenSpace.requestCreateNotification(recipient,
         notification, rsmNotificationCallback);
}

// version with templates defined
function rsmNotificationV2(recipient, game_id){
   // defined template:
   // Hey ${opponent}, ${sender} made their move, it's your turn!

   // setup all the data we'll need
   var template = "chad_template";
   var url = MyOpenSpace.NotificationButton.UrlTypes.CANVAS;
   var button_params = {
      "from" : "noti",
      "action" : "play",
      "game_id" : game_id
   };
   var button_text = "Play Now!";
   var template_params = { "opponent" : recipient };

   var param = {};

   param[MyOpenSpace.NotificationButton.Field.URL] = url;
   param[MyOpenSpace.NotificationButton.Field.PARAMS] = button_params;
   param[MyOpenSpace.NotificationButton.Field.TEXT] = button_text;

   // create a MyOpenSpace.NotificationButton object
   var button1 = MyOpenSpace.newNotificationButton(param);

   param = {};

   param[MyOpenSpace.Notification.Field.TITLE_ID] = template;
   param[MyOpenSpace.Notification.Field.BUTTONS] = [button1];
   param[MyOpenSpace.Notification.Field.TEMPLATE_PARAMS] = template_params;

   // create the MyOpenSpace.Notification object
   var notification = MyOpenSpace.newNotification(param);

   // send it off!
   MyOpenSpace.requestCreateNotification(recipient,
         notification, rsmNotificationCallback);
}

function rsmNotificationCallback(response){}

function rsmMessage(){
   var current_list = TTT.Lists.getCurrentList();
   var id = this.list_index;
   TTT.Lists.itemClicked = id;

   var this_app = opensocial.getEnvironment().currentApplication;
   var profile_link =
         this_app.getField(MyOpenSpace.Application.Field.PROFILE_URL);
   var image_link =
         this_app.getField(MyOpenSpace.Application.Field.ICON_LARGE);

   var subject = "I challenge you to a TTT duel!";

   var name = "";
   for(var i = 0; i < current_list.list.length; i++){
      if(current_list.list[i].getId() == id){
         name = current_list.list[i].getDisplayName();
         break;
      }
   }

   name = ("" === name) ? name : " " + name;

   var body = "Hey" + name + "! You've been challenged to ";
   body += "a game of Tic-Tac-Toe, click <a href='" + profile_link + "'>";
   body += "here</a> to accept the challenge!<center>";
   body += "<img src='" + image_link + "' /></center>";

   rsmWrapper(id, subject, body,
         opensocial.Message.Type.PRIVATE_MESSAGE, rsmMessageCallback);
}

function rsmMessageCallback(response){
   var div = TTT.Tabs.getCurrentContainer().firstChild;

   if(response && !response.hadError()){
      if(0 === response.getData()){
         if(div.innerHTML.indexOf("challenge cancelled...") < 0){
            div.innerHTML += "challenge cancelled...";
         }
      }
      else if(1 === response.getData()){
         div.innerHTML = "challenge sent!";
      }
   }
   else{
      log("Oops, there was an error, try refreshing the page!");
   }
}

function rsmBulletin(){
   var this_app = opensocial.getEnvironment().currentApplication;
   var profile_link =
         this_app.getField(MyOpenSpace.Application.Field.PROFILE_URL);
   var image_link =
         this_app.getField(MyOpenSpace.Application.Field.ICON_LARGE);

   var subject = "Social Tic-Tac-Toe is Here!";

   var body = "Hey everyone! I'm playing TTT online with my friends, come ";
   body += "<a href='" + profile_link + "'>play a game</a> with me!";
   body += "<center><img src='" + image_link + "' /></center>";

   rsmWrapper(opensocial.IdSpec.PersonId.VIEWER, subject, body,
         opensocial.Message.Type.NOTIFICATION, rsmBulletinCallback);
}

function rsmBulletinCallback(response){
   var div = TTT.Tabs.getCurrentContainer().firstChild;

   if(response && !response.hadError()){
      if(0 === response.getData()){
         if(div.innerHTML.indexOf(" cancelled...") < 0){
            div.innerHTML += " cancelled...";
         }
      }
      else if(1 === response.getData()){
         div.innerHTML = "bulletin sent";
      }
   }
   else{
      log("Oops, there was an error with the bulletin, try refreshing the page!");
   }
}

function rsaInviteClicked(){
   var id = this.list_index;

   if(TTT.Lists.itemClicked.constructor === Array){
      var found = false;
      for(var i = 0; i < TTT.Lists.itemClicked.length; i++){
         if(TTT.Lists.itemClicked[i] === id){
            found = true;
         }
      }

      if(!found){
         TTT.Lists.itemClicked.push(id);
      }
   }
   else{
      TTT.Lists.itemClicked = [id];
   }

   var clicked = TTT.Lists.getCurrentList().list_dom[id];
   clicked.style.backgroundColor = "yellow";
}

function requestShareAppWrapper(id, body, callback){
   // create the message object
   var reason = opensocial.newMessage(body);

   // initiate the share app modal
   opensocial.requestShareApp(id, reason, callback);
}

function rsaInviteButtonClicked(){
   // TTT.Lists.itemClicked contains the list of friends
   // that were clicked
   if(TTT.Lists.itemClicked.length > 0){

      // take the ID of the first friend that was clicked
      var id = TTT.Lists.itemClicked[0];

      // create some catchy body text
      var body = "Hi [recipient]... [sender] wants you to come";
      body += " play Tic-Tac-Toe, add [app] now!";

      // then pass the values into our wrapper
      requestShareAppWrapper(id, body, rsaWrapperCallback);
   }
}

function rsaWrapperCallback(response){
   // get the first clicked friend
   var current_list = TTT.Lists.getCurrentList();
   clicked = current_list.list_dom[TTT.Lists.itemClicked[0]];

   // check for some kind of bad response, and show an error message
   if(!clicked || !response || response.hadError()
             || -1 === response.getData()){
      log("Oops, there was an error, try refreshing the page!");
   }
   else{

      // was the popup cancelled?
      if(0 === response.getData()){

         // if so, stop looping through the list and revert
         // all the friend boxes to a white background
         for(var i = 0; i < TTT.Lists.itemClicked.length; i++){
            clicked = current_list.list_dom[TTT.Lists.itemClicked[i]];
            clicked.style.backgroundColor = "white";
         }

         // reset the list
         TTT.Lists.itemClicked = [];
      }
      else{

         // otherwise the share request was sent,
         // remove the first friend
         TTT.Lists.itemClicked.splice(0, 1);

         // remove the friend box from the DOM to
         // make it clear it was sent
         if(clicked.parentNode){
            clicked.parentNode.removeChild(clicked);
         }

         // start the process again
         rsaInviteButtonClicked();
      }
   }
}

function playClicked(){}

function setBackgroundClicked(){
   fetchPhotos(TTT.ListTypes.SET_BACKGROUND);
}

function inviteClicked(){
   fetchFriends(TTT.ListTypes.ALL_FRIENDS);
}

function challengeClicked(){
   fetchFriends(TTT.ListTypes.HASAPP_FRIENDS);
}

function photoClicked(){
   var item_container = TTT.Lists.getCurrentList().list_dom[this.list_index];
   var img_src = item_container.firstChild.firstChild.src;

   item_container.style.backgroundColor = "yellow";
   window.setTimeout(function(){ item_container.style.backgroundColor = "white"; }, 3000);

   var current_list = TTT.Lists.getCurrentList();

   var photo = null;
   for(var i = 0; i < current_list.list.length; i++){
      if(current_list.list[i].getField(current_list.id_key) === this.list_index){
         photo = current_list.list[i];
      }
   }

   if(photo !== null){
      currentGame.customBG.setCustomBG(photo);
   }
   else{
      log("Oops! Something went wrong with your photo selection, try refreshing.");
   }
}

function fetchPhotos(list_type){
   TTT.Lists.setCurrentList(list_type);
   var current_list = TTT.Lists.getCurrentList();

   fetchPhotosList(current_list.first, current_list.max, TTT.Lists.callback);
}

function fetchPhotosList(first, max, callback){
   var params = {};
   params[opensocial.DataRequest.PeopleRequestFields.FIRST] = first;
   params[opensocial.DataRequest.PeopleRequestFields.MAX] = max;

   var request = opensocial.newDataRequest();
   request.add(MyOpenSpace.DataRequest.newFetchPhotosRequest(opensocial.IdSpec.PersonId.VIEWER, params), TTT.RequestKeys.VIEWER_PHOTOS);
   request.send(callback);
}

function fetchFriends(list_type){
   TTT.Lists.setCurrentList(list_type);
   var current_list = TTT.Lists.getCurrentList();

   var filter = "";
   if(TTT.ListTypes.HASAPP_FRIENDS === list_type){
      filter = opensocial.DataRequest.FilterType.HAS_APP;
   }

   fetchFriendList(current_list.first, current_list.max, TTT.Lists.callback, filter);
}

function fetchFriendList(first, max, callback, filter, sort){
   // create the IdSpec object
   var params = {};
   params[opensocial.IdSpec.Field.USER_ID] =
         opensocial.IdSpec.PersonId.VIEWER;
   params[opensocial.IdSpec.Field.NETWORK_DISTANCE] = 1;
   var idspec = opensocial.newIdSpec(params);

   params = {};

   // set the paging parameters
   if(first){
      params[opensocial.DataRequest.PeopleRequestFields.FIRST] = first;
   }

   if(max){
      params[opensocial.DataRequest.PeopleRequestFields.MAX] = max;
   }


   // add any filters
   if(filter && filter.length > 0){
      params[opensocial.DataRequest.PeopleRequestFields.FILTER] = filter;
   }

   // add any sorts
   if(sort && sort.length > 0){
      params[opensocial.DataRequest.PeopleRequestFields.SORT_ORDER] = sort;
   }

   // create the DataRequest object
   var request = opensocial.newDataRequest();

   // add the request to the queue
   request.add(request.newFetchPeopleRequest(idspec, params),
         TTT.RequestKeys.VIEWER_FRIENDS);

   // send it off
   request.send(callback);
}




function getOtherNameData(){
	//debugger;
	var fld = document.getElementById("txtOtherUserID");
	var id = fld.value;

	var req = opensocial.newDataRequest();
	var fields = [ TTT.Keys.NAME, TTT.Keys.SCORE ];
	req.add(req.newFetchPersonAppDataRequest(opensocial.DataRequest.PersonId.VIEWER, fields), TTT.RequestKeys.VIEWERDATA);
	if(viewer == null){
		req.add(req.newFetchPersonRequest(id), "myotherkey");
	}

	req.send(getOtherNameDataCallback);

}

function getOtherNameDataCallback(data){
	if (data.hadError()) {
		return; //exit if nothing found
	}
	var x = data.get("myotherkey");
	var mydata = x.getData();
	//app data has an additional layer of indirection
	//we reassign mydata object to the data map object
	//circumvent viewer lookup by getting key
	var ok = false;
	for(var vkey in mydata){
		mydata = mydata[vkey];
		ok = true;
		break;
	}
	if(ok){
		var key
		for(key in mydata){
			myLocalAppData[key] = mydata[key];
		}
	}
}


/**
 * Obtains the selected radio from radio input control(s)
 * @param {Object} radioInput
 */
function getRadioValue(radioInput){
	  var checkedId=0, i;
	  if(radioInput){
	  	for(i=0; i < radioInput.length;i++){
			if(radioInput[i].checked){
				checkedId=i;
				break;
			}
		}
	  	return radioInput[checkedId].value;
	  }
	  return null;
}

/**
 * Obtains the selected option from a single item select list
 * @param {Object} selectInput
 */
function getSelectValue(selectInput){
	  if(!selectInput) return null;
	  if(selectInput.selectedIndex == -1){
	  	return null;
	  }
	  return selectInput[selectInput.selectedIndex].value;
}


/**
 * Loads a digg feed and displays it
 * @param {Object} useJsonP
 */
function loadFeed(){
    var feedUrl = "http://www.digg.com/rss/index.xml"; //default to digg
	var errElem = document.getElementById("feedItemErrors");
	errElem.innerHTML = "";

	var elem = document.getElementById("rssFeed");
	elem.innerHTML = "...Loading feed...";

	  var picked = document.forms[0].elements['feedSource'];
	  feedUrl = getSelectValue(picked);

	  var typeRadio = document.forms[0].elements['feedformat'];
	  var formatVal = getRadioValue(typeRadio);

	  var refreshFeed = document.getElementById("chkRefreshFeed").checked;
	  var interval = 0;
	  if(refreshFeed){
	  	interval = 3 * 60 * 1000; // every three minutes
	  }

	  var format;
	  var subformat = null;
	  switch(formatVal){
	  	case "FEED":
			format = gadgets.io.ContentType.FEED;
			break;
	  	case "DOM":
			format = gadgets.io.ContentType.DOM;
			break;
	  	case "DOMXSL":
			format = gadgets.io.ContentType.DOM;
			subformat = "XSL";
			break;
	  	default: //text
			format = gadgets.io.ContentType.TEXT;
			break;
	  }

	  loadFeedWithOptions(feedUrl, format, interval);
}

/**
 * MakeRequest call with resolved parameters.
 * @param {Object} url
 * @param {Object} format
 */
function loadFeedWithOptions(url, format, refreshInterval){
	// Initialize the request to treat it as a feed
	var params = {};
	params[gadgets.io.RequestParameters.CONTENT_TYPE] = format;
	params[gadgets.io.RequestParameters.NUM_ENTRIES] = 5;
	//	  params[gadgets.io.RequestParameters.GET_SUMMARIES] = true;
	//params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.TEXT;

	var callback = function(response){
			feedCallback(response, format);
	}
	gadgets.io.makeRequest(url, callback, params);

	if(refreshInterval && refreshInterval > 0){
		window.setTimeout(function(){loadFeedWithOptions(url, format, refreshInterval);}, refreshInterval);
	}
}

function feedCallback(response, format, subformat){
	var errElem = document.getElementById("feedItemErrors");
	errElem.innerHTML = "";
	if(response.errors && response.errors.length > 0){
		errElem.innerHTML = "Error: " + response.errors[0];
		showFeedResults(response.text, gadgets.io.ContentType.TEXT);
	}
	else{
		showFeedResults(response.data, format, subformat);
	}

}

/**
 * Display feed results.  Parsing depends on format specified.
 * @param {Object} feed
 * @param {Object} format
 */
function showFeedResults(feed, format, subformat){
	var elem = document.getElementById("rssFeed");
	elem.innerHTML = "";
	var titleElem = document.createElement("h3");
	titleElem.style.marginTop="0";
	titleElem.style.paddingTop="0";

	var title, copyright, items, desc;
	var i, entries;
	var line, link, body;
	var item;
	copyright=null;

	var tmpElem, tmp;
	var feedBody = document.createElement("div");

	if(format==gadgets.io.ContentType.FEED){
		title = feed.title;
		copyright = feed.copyright;
		items = feed.items;
		entries = Math.min(items.length, 5);

		//iterate feed items
		for(i=0; i < entries; i++){
			item = items[i];
			line = document.createElement("div");
			body = document.createElement("div");
			link = document.createElement("a");
			line.appendChild(link);
			link.href = item.link;
			link.target = "_blank";
			link.innerHTML = item.title;
			if(item.summary){
				body.innerHTML = item.summary;
			}
			else{
				body.innerHTML = item.description.substr(0, Math.min(120, item.description.length));
			}
			line.appendChild(body);

			feedBody.appendChild(line);
		}

	}
	else if (format == gadgets.io.ContentType.DOM && subformat == "XSL") {
		displayXmlData(feed, createXmlDocFromString(feedTransform),elem);
		return; //just exit after transform here
	}
	else if(format==gadgets.io.ContentType.DOM){
		/*
		 * Structure of an RSS XML document:
		 * /rss/channel/[title,link,description,...] | /[copyright]|item/**
		 */
		var allChannels =feed.getElementsByTagName("channel");
		var channel;
		if(allChannels && allChannels.length > 0){
			channel = allChannels[0];
			if(channel){
				title = getFirstDomNodeContents(channel.getElementsByTagName("title"));
				copyright = getFirstDomNodeContents(channel.getElementsByTagName("copyright"));
				items = channel.getElementsByTagName("item");
				if(items && items.length){
					entries = Math.min(5, items.length);
					for(i=0; i < entries; i++){
						item = items[i];
						line = document.createElement("div");
						body = document.createElement("div");
						link = document.createElement("a");
						line.appendChild(link);
						link.href = getFirstDomNodeContents(item.getElementsByTagName("link"));
						link.target = "_blank";
						link.innerHTML = getFirstDomNodeContents(item.getElementsByTagName("title"));
						desc = getFirstDomNodeContents(item.getElementsByTagName("description"));
						if(desc){
							body.innerHTML = desc.substr(0, Math.min(120, desc.length));
						}
						line.appendChild(body);
						feedBody.appendChild(line);
					}
				}
			}
		}
	}
	else { //text
		title = "[RAW RESPONSE]"
		var pre = document.createElement("pre");
		if (feed && typeof(feed) === "string") {
			//Apps are incorrectly escaped, so you need to construct the escape sequence
			var es = "&";
			var gt = "gt;";
			var lt = "lt;";
			tmp = feed.replace("<", es + lt, "g");
			tmp = tmp.replace(">", es + gt, "g");
			pre.innerHTML = tmp;
			feedBody.appendChild(pre);
		}
		else {
			feedBody.innerHTML = "Huh?";
		}
	}

	titleElem.innerHTML = title;

	elem.appendChild(titleElem);
	//honor the copyright
	if(copyright != null){
		var cp = document.createElement("div");
		cp.style.fontSize="7pt;"
		cp.style.color="#FFA";
		cp.style.fontWeight="bold";
		cp.style.fontFamily="Arial";
		cp.innerHTML = "Copyright " + copyright;
		elem.appendChild(cp);
	}
	elem.appendChild(feedBody);
}

/**
 * Convenience method to get first items from a nodelist
 * @param {Object} nodelist
 */
function getFirstDomNodeContents(nodelist){
	if(nodelist && nodelist.length > 0){
		return nodelist[0].textContent;
	}
	else{
		return null;
	}
}


/**
 * Word of the day
 */
function loadWordOfTheDay(){
    var feedUrl = "http://wordsmith.org/awad/rss1.xml";

      // Initialize the request to treat it as a feed
	  var params = {};
	  params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.FEED;
	  params[gadgets.io.RequestParameters.NUM_ENTRIES] = 1;
	  gadgets.io.makeRequest(feedUrl, loadWODCallback, params);
}

function loadWODCallback(response){
	var feed = response.data;
	var elem = document.getElementById("wordoftheday");
	var desc = document.getElementById("wodDesc");
	var copy = document.getElementById("wodcopyright");
	elem.innerHTML = "";
	copy.innerHTML = feed.copyright;
	if(response.errors && response.errors.length > 0){
		elem.innerHTML = "error"
	}
	else{
		var wod = feed.items[0];
		elem.innerHTML = "<a target='_blank' href=\"" + wod.link + "\">" + wod.title + "</a>";
		desc.innerHTML = wod.description;
	}

}


function searchImage(){
	var searchUrl = "http://search.yahooapis.com/ImageSearchService/V1/imageSearch?appid=YahooDemo&query=##term##&output=json"	;

	var term = document.getElementById("imageTerm").value;
	if(!term || term == "") term = "world";

	var searchUrl = searchUrl.replace("##term##", term);
	makeJsonpRequest(searchUrl, "loadSearchCallback");
}

function loadSearchCallback(data){
	// data.ResultSet.Result[@Title, @Url]

	var elem = document.getElementById("rssFeed");
	elem.innerHTML = "";

	var line, img;
	var result;
	for(var i=0;i < data.ResultSet.Result.length; i++){
		result = data.ResultSet.Result[i];
		line = document.createElement("div");
		img = document.createElement("img");
		line.appendChild(img);
		img.src = result.Url;
		img.setAttribute("height", "60");
		elem.appendChild(line);
	}
}



/**
 * Make a JSONP request to any url serving JSON data by utilizing
 * the google ajax feed service
 * @param {Object} url
 * @param {Object} callback
 */
function makeJsonpRequest(url, callback){
	var ajaxurl =url + "&callback=" + callback;

	var headElem = document.getElementsByTagName("head")[0];

	var scriptElem = document.createElement("script");
	scriptElem.src = ajaxurl;
	headElem.appendChild(scriptElem);
}





function loadInitializer(){
	try{
		loadWordOfTheDay();
	}
	catch(ex){}
	try{
		initializeGame();
	}
	catch(ex){}
	window.setTimeout(function(){loadFriendPicker();}, 200);

}

/**
 * Game Info object encapsulation for use when storing game
 * in the appdata store
 */
TTT.GameInfo = function(opponentId){
	this.opponentId = opponentId;
	this.dataSourceId = window.viewer.getId();
	this.boardLayout = null;
	this.moveNumber = 0;
	this.currentPlayer = 0;
	this.winner=0;
}


/**
 * Object controlling AppData-backed turn-based gaming.
 * This technique is described in Chapter 7 - Flushing and Fleshing
 */
TTT.AppDataPlay ={
	/**
	* Maximum number of active challenge games allowed.
	* Any unanswered games, then the oldest game will be removed
	* to allow for new games
	*/
	MAX_GAMES: 5,
	/**
	* Time (in seconds) that the system will wait between polling to see
	* if the opponent has made a move
	*/
	OPPONENT_POLLING_TIME: 10,

	/**
	* Maximum number of times the poll will fire without a response before
	* polling stops.
	*/
	MAX_POLLING_COUNT: 100,

	/**
	 * Keys into the AppData store
	 */
	Keys: {
		activeGames: "activeGames",
		appDataGetKey: "getData",
		finishedGames: "finishedGames"
	},

	/**
	 * Retrieves the gameInfo object for the specified opponent.
	 * If one doesn't exist, a new gameInfo object is created.
	 *
	 */
	getCurrentGameObject: function(opponentId){
		var result = null;
		if(this.myGameAppData && this.myGameAppData.activeGames != null){
			var items = this.myGameAppData.activeGames;
			var i;
			if(items.length){
				for(i=0; i < items.length; i++){
					if(items[i].opponentId == opponentId){
						result = items[i];
						break;
					}
				}
			}
			else{
			//is object
				var x;
				for(x in items){
					if(items[x].opponentId == opponentId){
						result = items[x];
						break;
					}
				}
			}
		}
		if(result == null){
			result = new TTT.GameInfo(opponentId);
			if(!this.myGameAppData.activeGames){
				this.myGameAppData.activeGames = [];
			}
			this.myGameAppData.activeGames.push(result);
		}

		return result;
	},

	/**
	 * Local cache of game AppData for Viewer.
	 */
	myGameAppData: {
		"activeGames":null
	},

	/**
	 * Local cache of opponent game AppData
	 */
	myOpponentAppData: {},

	/**
	 * Local cache of opponent current game
	 */
	myOpponentCurrentGame: {},


	/**
	 * Current opponent being played against
	 */
	currentOpponentId: null,

	/**
	 * Convenience method to same the data currently in this.myGameAppData
	 * to the backing AppData store.
	 */
	updateStoredGameObject: function(){
	//debugger;
		TTT.AppDataPlay.setAppDataKey(TTT.AppDataPlay.Keys.activeGames, TTT.AppDataPlay.myGameAppData[TTT.AppDataPlay.Keys.activeGames]);
	},

	/**
	 * Retrieve the current Viewer's AppData.
	 * This is an upgrade of the method introduced in
	 * Chapter 4 - persistence.
	 */
	getMyAppData: function (){
		var idparams = {};
		idparams[opensocial.IdSpec.Field.USER_ID] = opensocial.IdSpec.PersonId.VIEWER;
		idparams[opensocial.IdSpec.Field.NETWORK_DISTANCE] = 0;
		idparams[opensocial.IdSpec.Field.GROUP_ID] = opensocial.IdSpec.GroupId.SELF;

		var id = opensocial.newIdSpec(idparams);
		 var req = opensocial.newDataRequest();
		  req.add(req.newFetchPersonAppDataRequest(id, "*"), TTT.AppDataPlay.Keys.appDataGetKey);
		  req.send(function(data){
		  	TTT.AppDataPlay.loadAppDataCallback(data, TTT.AppDataPlay.myGameAppData);
		  });

	},

	/**
	 * Retrieve AppData for an opponent.
	 */
	getFriendGameData: function (friendId){
		var idparams = {};
		idparams[opensocial.IdSpec.Field.USER_ID] = opensocial.IdSpec.PersonId.VIEWER;
		idparams[opensocial.IdSpec.Field.NETWORK_DISTANCE] = 1;
		if(!friendId){
			friendId = window.friendPicker.selectedFriend.getId();
		}
		idparams[opensocial.IdSpec.Field.GROUP_ID] = friendId;

		//if MySpace fixes the IdSpec bug, use this impl
		/*
		if(!friendId){
			friendId = window.friendPicker.selectedFriend.getId();
		}
		idparams[opensocial.IdSpec.Field.USER_ID] = friendId;
		idparams[opensocial.IdSpec.Field.GROUP_ID] = opensocial.IdSpec.GroupId.SELF;
		*/
				
		
		if(TTT.AppDataPlay.currentOpponentId != friendId && window.currentGame){
			currentGame.clear();
		}

		if(TTT.AppDataPlay.isPollingForActiveGame && TTT.AppDataPlay.currentOpponentId != friendId){
			TTT.AppDataPlay.cancelPolling();
		}

		TTT.AppDataPlay.currentOpponentId = friendId;

		var id = opensocial.newIdSpec(idparams);
		var req = opensocial.newDataRequest();
		req.add(req.newFetchPersonAppDataRequest(id, "*"), TTT.AppDataPlay.Keys.appDataGetKey);
		  req.send(function(data){
		  	TTT.AppDataPlay.loadAppDataCallback(data, TTT.AppDataPlay.myOpponentAppData, TTT.AppDataPlay.selectedOpponentDataCallback);
		  });
	},

	cancelPolling: function(){
		window.clearTimeout(TTT.AppDataPlay.gamePollTimer);
		TTT.AppDataPlay.isPollingForActiveGame = false;
		TTT.AppDataPlay.currentGamePollCount = 0;
	},

	isPollingForActiveGame: false,

	/**
	 * Reference to any active timeout polls for game play
	 *
	 */
	gamePollTimer: null,

	currentGamePollCount: 0,

	/**
	 * Execute a test to check to see if the opponent has
	 * made a move.
	 *
	 */
	pollForOpponentUpdatedMove: function(){
		var thisObj = TTT.AppDataPlay;
		thisObj.gamePollTimer = null;
		thisObj.isPollingForActiveGame = false;
		thisObj.getFriendGameData(thisObj.currentOpponentId);

		if(thisObj.isPollingForActiveGame && thisObj.currentGamePollCount++ < thisObj.MAX_POLLING_COUNT){
			thisObj.startGameplayPolling();
		}
	gadgets.log("Polling fired: " + thisObj.currentGamePollCount);

	},

	pollSleepCount:0,

	/**
	 * Start to poll for opponent.
	 * Make sure we throttle to one active timer at a time.
	 */
	startGameplayPolling: function(){
	   gadgets.log("starting Poll");

		if(!this.currentOpponentId){
			return;
		}
		if(this.gamePollTimer && this.gamePollTimer > 0){
			if(TTT.AppDataPlay.pollSleepCount++ > 5){
				TTT.AppDataPlay.pollSleepCount = 0;
			return;
			}
		}

		this.isPollingForActiveGame = true;
		this.gamePollTimer = window.setTimeout(function(){
					TTT.AppDataPlay.gamePollTimer = 0;
					TTT.AppDataPlay.pollForOpponentUpdatedMove();
				}, (TTT.AppDataPlay.OPPONENT_POLLING_TIME * 1000));
	},

	/**
	 * Sets an appdata key for the current viewer
	 * @param {String} key
	 * @param {JSON object} value
	 */
	setAppDataKey: function (key, value) {

	  var req = opensocial.newDataRequest();
	  req.add(req.newUpdatePersonAppDataRequest(opensocial.IdSpec.PersonId.VIEWER, key, value), "set_" + key);
	  req.send(TTT.AppDataPlay.setAppDataCallbackHandler);
	},

	setAppDataCallbackHandler: function(data){
		if(data.hadError()){
			gadgets.log("Error in set: " + data);
		}
	},

	/**
	 * Callback wrapper method for loadAppData.
	 * This loads all the data into myLocalAppData
	 * and triggers the loadedCallback function, if specified
	 * @param {Object} data
	 * @param {Function} loadedCallback
	 */
	loadAppDataCallback: function (data, targetCollection, loadedCallback){
		//debugger;
		logDataError(data);
		if (data.hadError()) {
			return; //exit if nothing found
		}

		var mydata = data.get(TTT.AppDataPlay.Keys.appDataGetKey).getData();
		//app data has an additional layer of indirection
		//we reassign mydata object to the data map object
		//circumvent viewer lookup by getting key
		var ok = false;
		for(var vkey in mydata){
			mydata = mydata[vkey];
			ok = true;
			break;
		}
		if(ok){
			if(targetCollection == null){
				gadgets.log("Bad collection in load callback");
				targetCollection = {};
			}
			var key
			for(key in mydata){
				targetCollection[key] = mydata[key];
			}
		}
//debugger;
		if(loadedCallback && typeof(loadedCallback) == "function"){
			loadedCallback(targetCollection);
		}
	},

	/**
	*
	*/
	selectOpponent: function(opponentId){
		//debugger;
		this.getFriendGameData(opponentId);
	},

	/**
	 * Receives the data response when an opponent is selected
	 * and coordinates initializing the game.
	 */
	selectedOpponentDataCallback: function(data){
		if(!data){
			return;
		}
		//since we're in a callback, "this" has lost scope
		var thisObj = TTT.AppDataPlay;
		thisObj.cancelPolling();

		var myId = window.viewer.getId();
		//thisObj.myOpponentAppData = null;
		thisObj.myOpponentCurrentGame = null;
		var opponentStoredGames = data[thisObj.Keys.activeGames];
		if(opponentStoredGames != undefined && opponentStoredGames != null){
		//debugger;
			opponentStoredGames = gadgets.json.parse(opponentStoredGames);
			if(opponentStoredGames.length){
				var curGame;
				for(var i=0; i < opponentStoredGames.length; i++){
					curGame = opponentStoredGames[i];
					if(curGame.opponentId == myId){
						thisObj.myOpponentCurrentGame = curGame;
						break;
					}
				}
			}
		}
		var msg = "Playing: " + thisObj.currentOpponentId;

		var myGameInfo = thisObj.getCurrentGameObject(thisObj.currentOpponentId);
		var latestGameInfo;

		if(thisObj.myOpponentCurrentGame != null){

		//test for a cleared game
			var isNewGame = false;
			if(myGameInfo.winner != 0 && thisObj.myOpponentCurrentGame.moves < 3){
				thisObj.removeGame(thisObj.currentOpponentId);
				myGameInfo = thisObj.getCurrentGameObject(thisObj.currentOpponentId);
				isNewGame = true;
				latestGameInfo = myGameInfo;
			}
			else if(thisObj.myOpponentCurrentGame.winner != 0){
				isNewGame = true;
				latestGameInfo = myGameInfo;
			}

			//reconcile to latest game version
			if(!isNewGame){
				if(myGameInfo.moveNumber <= thisObj.myOpponentCurrentGame.moveNumber){
					latestGameInfo = thisObj.myOpponentCurrentGame;
					//sync the player's game board with most current board
					myGameInfo.boardLayout = latestGameInfo.boardLayout;
					myGameInfo.moveNumber = latestGameInfo.moveNumber;
					myGameInfo.currentPlayer = latestGameInfo.currentPlayer;
				}
				else{
					latestGameInfo = myGameInfo;
					//test for winner
				}
			}

			if(latestGameInfo.winner != 0){
				if(latestGameInfo.winner == myId){
					thisObj.updateGameStatusMessage("YOU WON!!!");
					myGameInfo.winner = myId;
				}
				else if(latestGameInfo.winner == -1){
					thisObj.updateGameStatusMessage("ITS A DRAW!!!");
					myGameInfo.winner = -1;
				}
				else{
					thisObj.updateGameStatusMessage("YOUR OPPONENT WON!!!");
					myGameInfo.winner = myGameInfo.opponentId;
				}
				thisObj.updateStoredGameObject();
				//debugger;
				thisObj.loadGame(latestGameInfo);
				thisObj.markGameFinished(latestGameInfo.opponentId);
				return;
			}

			thisObj.loadGame(latestGameInfo);


			//debugger;
			if(latestGameInfo.currentPlayer==myId) {
				//my turn
				msg += "<br />It is your turn";
			}
			else{
				msg += "<br />Still waiting on opponent";
				thisObj.startGameplayPolling();
			}

		}
		else{
			latestGameInfo = myGameInfo
			thisObj.loadGame(latestGameInfo);
			//save the new game back
			if(latestGameInfo.moveNumber==0){
				msg += "<br />New game started - make your move";
			}
			else{
				msg += "<br />Waiting for your opponent to move";
			}
		}
		thisObj.updateGameStatusMessage(msg);
	},

	/**
	 * Loads the specified gameInfo object into
	 * window.currentGame and updates the board
	 */
	loadGame: function(gameInfo){
		if(currentGame){
			currentGame.clear();
		}
		else{
			currentGame = new TTT.Game();
		}
		currentGame.isHumanOpponent = true; //flag to not make computer moves
		var myId = window.viewer.getId();

		var fromMe = (gameInfo.dataSourceId == myId);

		if(gameInfo.boardLayout){
			var moves = gameInfo.boardLayout.split("|");
			var nOneMark, nZeroMark;
			if(fromMe){
				nOneMark = TTT.Players.Human;
				nZeroMark = TTT.Players.Computer;
			}
			else{
				nOneMark = TTT.Players.Computer;
				nZeroMark = TTT.Players.Human;
			}

			//1 == game owner, 0 == viewer
			for(var i=0; i < moves.length; i++){
				if(moves[i]==""){
					continue;
				}
				else{
					if(moves[i]=="1"){
						currentGame.currentPlayer = nOneMark;
						currentGame.makeMove(nOneMark, i);
					}
					else{
						currentGame.currentPlayer = nZeroMark;
						currentGame.makeMove(nZeroMark, i);
					}
				}
			}

			//now init to current player
			//debugger;
			if(gameInfo.currentPlayer == myId){
				currentGame.currentPlayer = TTT.Players.Human;
			}
			else{
				currentGame.currentPlayer = TTT.Players.Computer;
			}

		}

	},

	markGameFinished: function(opponentId){
		this.setAppDataKey(TTT.AppDataPlay.Keys.finishedGames, gadgets.json.stringify(opponentId));
	},

	removeGame: function(opponentId){
		if(this.myGameAppData && this.myGameAppData.activeGames != null){
			var items = this.myGameAppData.activeGames;
			var newItems = [];
			var i;
			if(items.length){
				for(i=0; i < items.length; i++){
					if(items[i].opponentId != opponentId){
						newItems.push(items[i]);
					}
				}
			}
			else{
			//is object
				var x;
				for(x in items){
					if(items[x].opponentId != opponentId){
						newItems.push(items[x]);
					}
				}
			}
		}
		this.myGameAppData.activeGames = newItems;
		this.updateStoredGameObject();
	},

	clearGame: function(opponentId){
		//debugger;
		this.cancelPolling();
		var gameInfo = this.getCurrentGameObject(this.currentOpponentId);
		var myId = window.viewer.getId();
		this.markGameFinished(this.currentOpponentId);
		this.removeGame(this.currentOpponentId);
		if(currentGame){
			currentGame.clear();
		}
	},

	clearAllGames: function(){
		TTT.AppDataPlay.cancelPolling();
		TTT.AppDataPlay.myGameAppData[TTT.AppDataPlay.Keys.activeGames] = [];
		TTT.AppDataPlay.setAppDataKey(TTT.AppDataPlay.Keys.activeGames, {});
		TTT.AppDataPlay.setAppDataKey(TTT.AppDataPlay.Keys.finishedGames, "");
	},


	/**
	* Serializes the board information into a string for storage
	*/
	getGameMovesString: function(game){
		var i=0;
		var result = "";
		for(i=0; i < 9; i++){
			if(game.moves[i] === undefined || game.moves[i] == "" || game.moves[i] == null){
				result += "|";
			}
			else{
				if(game.moves[i] == TTT.Players.Computer){
					result += "0|";
				}
				else{
					result += "1|";
				}
			}
		}
		return result;
	},


	/**
	 * Displays a message regarding game play
	 */
	updateGameStatusMessage: function(message){
		var elem = document.getElementById("gameStatus");
		if(!elem) return;
		elem.innerHTML = message;
	}



}


if(!gadgets.log){
	gadgets.log = function(msg){
	var el = document.getElementById("log");
	if(!el) return;
	var m = document.createElement("div");
	m.innerHTML = msg;
	el.appendChild(m);
	}
}



//register to look for initial data
gadgets.util.registerOnLoadHandler(loadInitializer);




//register to look for initial data
gadgets.util.registerOnLoadHandler(function(){ TTT.Tabs.selectTab(0); });

</script>

<div id="overlay"></div>
<div id="light_box_container"><div id="light_box"></div></div>

<div class="container">
   <h1>OpenSocial Tic-Tac-Toe</h1>

   <div>
      <div class="left tab_spacer small_width">&nbsp;</div>
      <div id="tab0" class="left tab" onclick="TTT.Tabs.selectTab(0);">Play</div>
      <div id="tab1" class="left tab" onclick="TTT.Tabs.selectTab(1);">Invite</div>
      <div id="tab2" class="left tab" onclick="TTT.Tabs.selectTab(2);">Challenge</div>
      <div id="tab3" class="left tab tab_last" onclick="TTT.Tabs.selectTab(3);">Set Background</div>
      <div class="left tab_spacer large_width">&nbsp;</div>
      <div class="clear"></div>
   </div>
</div>

<div id="play_container" class="container">
   <div class="left">
   <div id="gameStatus"></div>
       <table id="gameboard" onclick="makePlayerMove(event)">

           <tr>
               <td gamecell="0"></td>
               <td gamecell="1"></td>
               <td gamecell="2"></td>
           </tr>
           <tr>
               <td gamecell="3"></td>
               <td gamecell="4"></td>
               <td gamecell="5"></td>

           </tr>
           <tr>
               <td gamecell="6"></td>
               <td gamecell="7"></td>
               <td gamecell="8"></td>
           </tr>
       </table>
   </div>

   <div id="right_column">

<div id="infoblock">
      <div id="myinfo" class="player_info right_column">
         <div class="left"><a href="javascript:openProfile();"><img class="profile_image" src="http://x.myspacecdn.com/images/no_pic.gif" /></a></div>
         <div><div></div><div></div><div></div><div class="more"><a href="javascript:TTT.LightBox.show();">More...</a></div></div>
         <div class="clear"></div>
      </div>

      <div id="opponentinfo" class="player_info right_column hide">
         <div class="left"><a href="javascript:openProfile();"><img class="profile_image" src="http://x.myspacecdn.com/images/no_pic.gif" /></a></div>
         <div><div></div><div></div><div></div><div class="more"><a href="javascript:TTT.LightBox.show();">More...</a></div></div>
         <div class="clear"></div>
      </div>
  	  <br />


<form onsubmit="return false;">

<textarea id="txtName" name="name" ></textarea>
<button onclick="setNameData()">Set Name</button>


	    	  <br />
  	  <br />
	  <div id="availableGames"></div>
	  <div id="opponentPicker"></div>

      <div class="clear"></div>



   <div class="clear">

   <button onclick="initializeGame();">New Game</button>&nbsp;
   <button onclick="TTT.AppDataPlay.getMyAppData()">Get My Data</button>
   <button onclick="TTT.AppDataPlay.clearGame(window.friendPicker.selectedFriend.getId())">Clear game</button>
   <button onclick="TTT.AppDataPlay.clearAllGames()">Clear All games</button>
   </div>



<h3 style="color:#388;">Word of the day is: <span id="wordoftheday"></span></h3>
<div id="wodDesc"></div>
<div style="font-size:8pt;">Wordsmith.org word of the day</div>
<div id="wodcopyright" style="font-size:8pt;"></div>
<div id="feedItemControl">
	<h1>Feed Items</h1>
	<div id="feedItemErrors"></div>
	<div>
		<label for="selFeedSource" >Feed</label>
		<select name="feedSource" id="selFeedSource" >
			<option value="http://wordsmith.org/awad/rss1.xml">Word of the Day</option>
			<option value="http://feeds.digg.com/digg/popular.rss" selected="selected">Digg</option>
			<option value="http://news.cnet.com/8300-1009_3-83.xml">CNet</option>
			<option value="http://feeds2.feedburner.com/ICanHasCheezburger">LOL Cats</option>
			<option value="http://feeds2.feedburner.com/Mashable">Mashable</option>
			<option value="http://rss.slashdot.org/Slashdot/slashdot">Slashdot</option>
			<option value="http://rss.hulu.com/HuluRecentlyAddedVideos?format=xml">Hulu</option>

		</select>

		<div>
			<input type="checkbox" id="chkRefreshFeed" ><label for="chkRefreshFeed">Auto Refresh Feed</label>
		</div>

	</div>
		<input type="radio" name="feedformat" value="FEED" checked="checked">Feed</input>
		<input type="radio" name="feedformat" value="DOM">XML</input>
		<input type="radio" name="feedformat" value="TEXT">Text</input>
		<br />

	<button onclick="loadFeed()">Load Feed</button><br />
	<input id="imageTerm" />
	<button onclick="searchImage()">Search Images</button><br />

</form>
<div id="rssFeed">
</div></div>


      <div id="game_list" class="right_column hide"></div>

      <div id="log" style="position:relative;bottom:3px;left:10px;width:200px;height:200px;overflow:auto;z-index:0;"></div>

   </div>

</div>

<div id="invite_container" class="container hide"><div><button onclick="rsmBulletin();">Send a Bulletin!</button>&nbsp;<button onclick="rsaInviteButtonClicked();">Send Invite(s)</button></div></div>

<div id="challenge_container" class="container hide"><div></div></div>

<div id="set_background_container" class="container hide"><div></div></div>

<div id="messages"></div>

<style>
   img
   {
      border:0;
   }
   body
   {
      margin:0;
      padding:0;
      font-family:Verdana;
      font-size:12pt;
   }

   #gameboard
   {
   	margin:10px;
      border-collapse: collapse;
      border:2px solid black;
      padding:5px;
      margin-bottom:5px;
      empty-cells:show;
      font-size: 40pt;
      font-family:Verdana;
      font-weight:bold;
      color:#fff;
      cursor:pointer;
   }
   #gameboard td
   {
      color:#fff;
      border:1px solid white;
      text-align:center;
      width:120px;
      height:120px;
   }
   .container
   {
   	margin:10px;
   }
   .tab
   {
      border:solid 1px black;
      border-right:0;
      padding:5px 5px 4px;
      cursor:pointer;
      background-color:#ddd;
   }
   .tab_last
   {
      border-right:solid 1px black;
   }
   .tab_spacer
   {
      border-bottom:solid 1px black;
      padding:5px;
   }
   .small_width
   {
      width:40px;
   }
   .large_width
   {
      width:50%;
   }
   .tab_selected
   {
      border-bottom:0;
      background-color:white;
   }
   .friend
   {
      border:solid 1px black;
      margin:5px;
      padding:5px;
      float:left;
      width:90px;_width:102px;
      overflow:hidden;
      text-align:center;
      height:160px;
      cursor:pointer;
   }
   .photo
   {
      border:solid 1px black;
      margin:5px;
      padding:5px;
      float:left;
      width:130px;_width:142px;
      overflow:hidden;
      text-align:center;
      height:130px;_height:142px;
      cursor:pointer;
   }
   .photo img
   {
      width:130px;
      height:110px;
   }
   .pager
   {
      font-size:9pt;
   }
   .pager span
   {
      color:blue;
      cursor:pointer;
   }
   #messages
   {
      height:20px;
      width:80px;
      overflow:auto;
      color:Red;
	  position:absolute;
	  left:10px;
	  top:750px;
   }
   #right_column
   {
      float:right;
      width:500px;
   }
   .right_column
   {
      float:right;
      margin:5px;
      width:100%;
   }
   .player_info
   {
      border:solid 2px black;
      padding:5px;
   }
   .profile_image
   {
      margin:5px;
      border:solid 1px black;
   }
   .more a
   {
      line-height:40px;
      padding:5px;
      border:solid 1px black;
      cursor:pointer;
      text-decoration:none;
      color:Black;
      background-color:White;
   }
   #overlay
   {
   	display:none;
   	width:100%;
   	height:100%;
   	background-color:Black;
   	left:0;
   	top:0;
   	z-index:100000;
   	position:absolute;
   	opacity:0.6;
   	filter: alpha(opacity=60);
   }
   #light_box_container
   {
   	width:100%;
   	left:0;
   	position:absolute;
   	top:70px;
   	z-index:100001;
   	text-align:center;
   	display:none;
   }
   #light_box
   {
   	margin:auto;
   	width:650px;
   	background-color:White;
   	border:solid 4px black;
   	text-align:left;
   }
   #player_bio
   {
   	margin:5px;
   }
   #player_bio_title
   {
   	border-bottom:solid 1px black;
   	font-weight:bold;
   	padding:1px 0 5px 0;
   	text-align:center;
   }
   #bio_close
   {
   	border-top:solid 1px black;
   	margin-top:5px;
   	padding:3px;
   	text-align:center;
   }
   #bio_close a
   {
   	text-decoration:none;
   }
   .clear
   {
      clear:both;
   }
   .left
   {
      float:left;
   }
   .right
   {
      float:right;
   }
   .hide{

      display:none;
   }
</style>
